﻿using System.Threading;
using LockIntegralType = System.Int32;
using System;
// I really want to use 64 bit variables, but in my computer (32 bit) it is 50% slower.
// So, I am keeping the 32 bit one. On 64 bits, the writeBitShift can be 48, the upgradeBitShift is 32.

namespace Pfz.Threading
{
	/// <summary>
	/// A "real slim" reader writer lock.
	/// Many readers can read at a time and only one writer is allowed.
	/// Reads can be recursive, but a try to a recursive write will cause a dead-lock.
	/// Note that this is a struct, so don't assign it to a local variable.
	/// </summary>
	public struct YieldReaderWriterLockSlim
	{
		#region Consts
			private const int _writeBitShift = 24;
			private const int _upgradeBitShift = 16;

			private const LockIntegralType _writeLockValue = ((LockIntegralType)1) << _writeBitShift;
			private const LockIntegralType _writeUnlockValue = -_writeLockValue;
			private const LockIntegralType _upgradeLockValue = ((LockIntegralType)1) << _upgradeBitShift;
			private const LockIntegralType _upgradeUnlockValue = -_upgradeLockValue;
			private const LockIntegralType _allReadsValue = _upgradeLockValue-1;
			private const LockIntegralType _someExclusiveLockValue = _writeLockValue | _upgradeLockValue;
			private const LockIntegralType _someExclusiveUnlockValue = -_someExclusiveLockValue;
		#endregion

		#region Fields
			private LockIntegralType _lockValue;
		#endregion

		#region EnterReadLock
			/// <summary>
			/// Enters a read lock.
			/// </summary>
			public void EnterReadLock()
			{
				while(true)
				{
					LockIntegralType result = Interlocked.Increment(ref _lockValue);
					if ((result >> _writeBitShift) == 0)
						return;

					Interlocked.Decrement(ref _lockValue);

					while(true)
					{
						#if SILVERLIGHT
							Thread.Sleep(1);
						#else
							Thread.Yield();
						#endif

						result = Interlocked.CompareExchange(ref _lockValue, 1, 0);
						if (result == 0)
							return;

						if ((result >> _writeBitShift) == 0)
							break;
					}
				}
			}
		#endregion
		#region ExitReadLock
			/// <summary>
			/// Exits a read-lock. Take care not to exit more times than you entered, as there is no check for that.
			/// </summary>
			public void ExitReadLock()
			{
				Interlocked.Decrement(ref _lockValue);
			}
		#endregion

		#region EnterUpgradeableLock
			/// <summary>
			/// Enters an upgradeable lock (it is a read lock, but it can be upgraded).
			/// Only one upgradeable lock is allowed at a time.
			/// </summary>
			public void EnterUpgradeableLock()
			{
				while(true)
				{
					LockIntegralType result = Interlocked.Add(ref _lockValue, _upgradeLockValue);
					if ((result >> _upgradeBitShift) == 1)
						return;

					Interlocked.Add(ref _lockValue, _upgradeUnlockValue);

					while(true)
					{
						#if SILVERLIGHT
							Thread.Sleep(1);
						#else
							Thread.Yield();
						#endif

						result = Interlocked.CompareExchange(ref _lockValue, _upgradeLockValue, 0);
						if (result == 0)
							return;

						if ((result >> _upgradeBitShift) == 0)
							break;
					}
				}
			}
		#endregion
		#region ExitUpgradeableLock
			/// <summary>
			/// Exits a previously obtained upgradeable lock.
			/// </summary>
			public void ExitUpgradeableLock()
			{
				Interlocked.Add(ref _lockValue, _upgradeUnlockValue);
			}
		#endregion

		#region UpgradeToWriteLock
			/// <summary>
			/// upgrades to write-lock. You must already own a Upgradeable lock and you must first exit the write lock then the Upgradeable lock.
			/// </summary>
			public void UpgradeToWriteLock()
			{
				LockIntegralType result = Interlocked.Add(ref _lockValue, _writeLockValue);

				while((result & _allReadsValue) != 0)
				{
					#if SILVERLIGHT
						Thread.Sleep(1);
					#else
						Thread.Yield();
					#endif

					result = Interlocked.CompareExchange(ref _lockValue, 0, 0);
					//result = Interlocked.Read(ref _lockValue);
				}
			}
		#endregion
		#region ExitUpgradedLock
			/// <summary>
			/// Releases the Upgradeable lock and the upgraded version of it (the write lock)
			/// at the same time.
			/// Releasing the write lock and the upgradeable lock has the same effect, but
			/// it's slower.
			/// </summary>
			public void ExitUpgradedLock()
			{
				Interlocked.Add(ref _lockValue, _someExclusiveUnlockValue);
			}
		#endregion

		#region EnterWriteLock
			/// <summary>
			/// Enters write-lock.
			/// </summary>
			public void EnterWriteLock()
			{
				LockIntegralType result = Interlocked.Add(ref _lockValue, _writeLockValue);
				if (result == _writeLockValue)
					return;

				// we need to try again.
				Interlocked.Add(ref _lockValue, _writeUnlockValue);
				for(int i=0; i<100; i++)
				{
					#if SILVERLIGHT
						Thread.Sleep(1);
					#else
						Thread.Yield();
					#endif

					result = Interlocked.CompareExchange(ref _lockValue, _writeLockValue, 0);
					if (result == 0)
						return;

					// try to be the first locker.
					if ((result >> _writeBitShift) == 0)
						break;
				}

				// From this moment, we have priority.
				while(true)
				{
					result = Interlocked.Add(ref _lockValue, _writeLockValue);
					if (result == _writeLockValue)
						return;

					if ((result >> _writeBitShift) == 1)
					{
						// we obtained the write lock, but there may be readers,
						// so we wait until they release the lock.
						while(true)
						{
							#if SILVERLIGHT
								Thread.Sleep(1);
							#else
								Thread.Yield();
							#endif

							result = Interlocked.CompareExchange(ref _lockValue, 0, 0);
							//result = Interlocked.Read(ref _lockValue);
							if (result == _writeLockValue)
								return;
						}
					}
					else
					{
						// we need to try again.
						Interlocked.Add(ref _lockValue, _writeUnlockValue);
						while(true)
						{
							#if SILVERLIGHT
								Thread.Sleep(1);
							#else
								Thread.Yield();
							#endif

							result = Interlocked.CompareExchange(ref _lockValue, _writeLockValue, 0);
							if (result == 0)
								return;

							// try to be the first locker.
							if ((result >> _writeBitShift) == 0)
								break;
						}
					}
				}
			}
		#endregion
		#region ExitWriteLock
			/// <summary>
			/// Exits write lock. Take care to exit only when you entered, as there is no check for that.
			/// </summary>
			public void ExitWriteLock()
			{
				Interlocked.Add(ref _lockValue, _writeUnlockValue);
			}
		#endregion
	}
}
